'''
Created on 6.10.2011

@author: mphkh
'''
from datetime import date
from daycount import *
from daterolling import *
from calendar import monthrange

def daysbetween(startdate, enddate, basis):
    '''
    Calculate the the number of days between two dates (the startdate and enddate dates).
    Parameters: 
        startdate:    start date
        enddate:      end date
        basis:        DayCountBasis object with basis to use 
    Returns:
        Number of days between the two dates.
    '''
    result = 0
    if basis == DayCountBasis.basis_act360:
        result = (enddate - startdate).days
    elif basis == DayCountBasis.basis_act365:
        result = (enddate - startdate).days
    elif basis == DayCountBasis.basis_actact:
        if startdate.year != enddate.year:
            firstday = date(enddate.year, 1, 1)
            lastday = date(startdate.year, 12, 31)
            result = (lastday - startdate).days + (int(enddate.year) - int(startdate.year) - 1) + (enddate - firstday).days
        else:
            result = (enddate - startdate).days
    elif basis == DayCountBasis.basis_30360:
        d1 = startdate.day
        d1 = int(d1)
        d2 = enddate.day
        d2 = int(d2)
        if d1 == 31:
            d1 -= 1
        if d2 == 31:
            d2 -= 1
        result = (int(d2) - int(d1)) + 30.0*(int(enddate.month) - int(startdate.month)) + 360.0*(int(enddate.year) - int(startdate.year))
    else:
        raise RuntimeError, "Unsupported basis"
    return result


def monthsbetween(fromdate, todate):
    '''
    Number of months between dates, we assume fromdate<todate
    Parameters: 
        fromdate:  start date
        todate:    end date
    Returns:
        number of months between dates
    '''
    count = 0
    if todate.year > fromdate.year:
        count = (todate.year - fromdate.year)*12
    count = count + (todate.month - fromdate.month)
    return count
       

def addmonth(ddate, monthstoadd):
    '''
    Adding months to a date, we assume monthstoadd>=0
    Parameters: 
        ddate:       date to add months too
        monthstoadd: number of months to add
    Returns:
        date where ddate has been advanced by monthstoadd
    '''
    year    = ddate.year
    month   = ddate.month
    day     = ddate.day
    newmonth=month+monthstoadd
    if newmonth>12:
        year+=1
        newmonth-=12
    if newmonth<=12:
        return date(year,newmonth,day)
    else:
        return addmonth(date(year,month,day),monthstoadd-12)

def submonth(ddate, monthstosub):
    '''
    Subtracting months from a date, we assume monthstosub<=0
    Parameters: 
        ddate:        date to sumbtract months from
        monthstosub:   number of months to subtract
    Returns:
        date where ddate has been put back by monthstosub months
    '''
    year    = ddate.year
    month   = ddate.month
    day     = ddate.day
    newmonth=month-monthstosub
    if newmonth<=0:
        year-=1
        newmonth+=12
    if newmonth>0:
        return date(year,newmonth,day)
    else:
        return submonth(date(year,month,day),monthstosub-12)
    

def addmonth2(ddate, monthstoadd, caldar, daterollingconvention, businessdays=1):
    '''
    Adding months to a date, we assume monthstoadd>=0 (whole numbers)
    Based on BBA Libor rules for value and maturity dates. We accept ddate as the value date to use.
    1) We account for holidays according to caldar
    2) We dont restrict daterollingconvention and therefor not restricted to modified following business day convention
    3) For all maturity days, they cannot roll-over into a new calendar month from the value date. We roll back to
       latest available business day in month.
    (NOTE: By above, we 
    Parameters: 
        ddate:                   date to add months too
        monthstoadd:             number of months to add
        caldar:                  calendar to use (instance of Calendar)
        ddaterollingconvention:  date rolling convention (actual, modified, modified following, 
                                                          preceding, modified preceding)
        businessdays:            if businessdays=1, we roll back by 2) and 3) according to caldar business days, 
                                 else we don't but use last day in maturity month if ddate day is greater and actual 
                                 daterolling applies for all other daytes (no daterolling)
    Returns:
        date where ddate has been advanced by monthstoadd
    '''
    year    = ddate.year
    month   = ddate.month
    day     = ddate.day
    addyear = 0
    monthcount = month + monthstoadd
    
    while monthcount > 12:
        monthcount -= 12
        addyear +=1
    
    newmonth = monthcount
    newyear = year + addyear
    
    newdate = 0
    
    temp = monthrange(year, month)
    daysinmonth = temp[1]
    
    temp = monthrange(newyear, newmonth)
    newdaysinmonth = temp[1]
    
    lastbusdayofmonth =  preceding(caldar, date(year, month, daysinmonth))

    if businessdays == 1:
        if (day == lastbusdayofmonth.day) or (day>newdaysinmonth):
            newlastbusdayofmonth =  preceding(caldar, date(newyear, newmonth, newdaysinmonth))
            newdate = date(newyear, newmonth, newlastbusdayofmonth.day)
        else:
            newdate = date(newyear, newmonth, day)
            if daterollingconvention == DateRollingConvention.following:
                newdate = following(caldar, newdate)
            elif daterollingconvention == DateRollingConvention.modifiedfollowing:
                newdate = modifiedfollowing(caldar, newdate)
            elif daterollingconvention == DateRollingConvention.preceding:
                newdate = preceding(caldar, newdate)
            elif daterollingconvention == DateRollingConvention.modifiedpreceding:
                newdate = modifiedpreceding(caldar, newdate)
            else:
                newdate = date(newyear, newmonth, day)
    else:
        if day>newdaysinmonth:
            newdate = date(newyear, newmonth, newdaysinmonth)
        else:
            newdate = date(newyear, newmonth, day)
    return newdate
